<!DOCTYPE html>
<html>
  <head>
    <title>
      AudioBufferSourceNode - playbackRate test
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      // Any sample rate mutiple of 128 is valid for this test, but here it uses
      // 48000Hz because it is a commonly used number that happens to be
      // multiple of 128.
      let sampleRate = 32768;

      // The test iterates over 60 pitches starting from 36. (MIDI pitch of C2)
      let fundamentalPitch = 36;
      let numberOfPitches = 60;

      let noteDuration = Math.floor(0.025 * sampleRate) / sampleRate;
      let totalDuration = noteDuration * numberOfPitches;

      // Test constraints for each octave.
      let testConstraints = [
        {thresholdSNR: 97.215, thresholdDiffULP: 0.6446},
        {thresholdSNR: 97.212, thresholdDiffULP: 0.6446},
        {thresholdSNR: 97.217, thresholdDiffULP: 0.6446},
        {thresholdSNR: 97.196, thresholdDiffULP: 0.6485},
        {thresholdSNR: 97.074, thresholdDiffULP: 0.6915}
      ];

      function pitchToFrequency(midiPitch) {
        return 440 * Math.pow(2, (Math.floor(midiPitch) - 69) / 12);
      }

      function pitchDiffToPlaybackRate(midiPitchDiff) {
        return Math.pow(2, midiPitchDiff / 12);
      }

      function createSineWaveBuffer(context, frequency, duration) {
        let buffer = context.createBuffer(1, duration * sampleRate, sampleRate);
        let data = buffer.getChannelData(0);
        let omega = 2 * Math.PI * frequency / sampleRate;
        for (let i = 0; i < data.length; i++)
          data[i] = Math.sin(omega * i);

        return buffer;
      }

      // This is the fundamental buffer for playbackRate modulation. The
      // duration of this buffer is arbitrary but long enough to produce the
      // sound without running short.
      let fundamentalBuffer;

      // A unit test consists of 2 sources: the 'actual' source runs a buffer
      // with the playback rate modulated and the 'expected' source runs a
      // mathmatically generated sound buffer.
      function runUnitTest(context, noteStart, notePitch) {
        let actualSrc = context.createBufferSource();
        let expectedSrc = context.createBufferSource();
        let merger = context.createChannelMerger(2);

        actualSrc.buffer = fundamentalBuffer;
        expectedSrc.buffer = createSineWaveBuffer(
            context, pitchToFrequency(notePitch), noteDuration);
        actualSrc.playbackRate.value =
            pitchDiffToPlaybackRate(notePitch - fundamentalPitch);

        actualSrc.connect(merger, 0, 0);
        expectedSrc.connect(merger, 0, 1);
        merger.connect(context.destination);

        actualSrc.start(noteStart);
        actualSrc.stop(noteStart + noteDuration);
        expectedSrc.start(noteStart);
        expectedSrc.stop(noteStart + noteDuration);
      }


      // Test if AudioBufferSourceNode.playbackRate can playback at different
      // rates properly.
      audit.define('playbackrate-test', (task, should) => {
        let context =
            new OfflineAudioContext(2, totalDuration * sampleRate, sampleRate);
        fundamentalBuffer = createSineWaveBuffer(
            context, pitchToFrequency(fundamentalPitch), totalDuration);

        // Schedule tests up to 60 pitches above from the fundamental pitch.
        for (let iteration = 0; iteration < numberOfPitches; iteration++)
          runUnitTest(
              context, noteDuration * iteration, fundamentalPitch + iteration);

        // Once the rendering is complete, split the buffer into 5 octaves. Then
        // perform the SNR and the maximum difference ULP check for each octave
        // with different constraints.
        context.startRendering()
            .then(function(renderedBuffer) {
              let actual = renderedBuffer.getChannelData(0);
              let expected = renderedBuffer.getChannelData(1);
              let octaveLength = Math.floor(noteDuration * 12 * sampleRate);

              for (let i = 0; i < numberOfPitches / 12; i++) {
                let start = i * octaveLength;
                let end = (i + 1) * octaveLength;
                let octaveActual = actual.subarray(start, end);
                let octaveExpected = expected.subarray(start, end);

                compareBuffersWithConstraints(
                    should, octaveActual, octaveExpected, {
                      prefix: i,
                      thresholdSNR: testConstraints[i].thresholdSNR,
                      thresholdDiffULP: testConstraints[i].thresholdDiffULP
                    });
              }

            })
            .then(() => task.done());
      });

      audit.run();
    </script>
  </body>
</html>
